package gui;

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.File;
import java.util.Arrays;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import ai.human.PassKnowBase;

import common.IObserver;

import dao.DataLoader;
import data.Data.PlayerState;
import data.Data.TeamID;
import data.Player;
import data.Position;
import data.selection.SelectionObject;
import engine.GameEngine;
import engine.Pitch;
import engine.Pitch.MapContainer;
import engine.Pitch.PitchUpdateNotification;
import gui.customControl.LogWindow;
import gui.customControl.PitchCell;

public class PitchGUI extends JFrame implements IObserver {
	private static final long serialVersionUID = 1L;

	/**
	 * Instance.
	 */
	private static PitchGUI instance;

	public enum ButtonCardState {
		DISABLED, START_GAME, END_TURN
	}

	boolean windowOpened = false;

	JComboBox m_injured1;
	JComboBox m_ko1;
	JComboBox m_reserved1;

	JComboBox m_injured2;
	JComboBox m_ko2;
	JComboBox m_reserved2;

	// FIXME: We probably need more than just one label
	JLabel m_status;
	JLabel m_turn;
	JLabel m_score;

	JButton debugStepButton;
	private boolean debugStep = false;
	JButton resetButton;
	private static boolean automaticMode = true;
	JCheckBox automaticModeCheckbox;
	JButton intervalButton;
	private static List<Integer> INTERVALS = Arrays.asList(new Integer[] { 1000, 500, 250, 50, 1 });
	private static int interval = 1;

	// start gane and end turn buttons
	JButton m_endTurn;
	JButton m_startGame;
	JPanel m_cardsPanel;

	PitchBoardGUI m_board;

	// File chooser
	final JFileChooser m_fileChooser = new JFileChooser("data");
	File m_currentFile = null;

	Object obj;

	MapContainer map_handler;
	ButtonHandler m_buttonHandler = new ButtonHandler();

	// the top menu bar
	GameMenuBar m_menuBar;

	// The action highlight related to the action selected
	SelectionObject m_actionHighlight = null;

	/**
	 * Constructor
	 * 
	 * @param name
	 *            - the title of the window
	 */
	private PitchGUI(final String name, final boolean showGUI) {
		super(name);
		PitchGUI.instance = this;
		setResizable(true);
		setDefaultCloseOperation(DISPOSE_ON_CLOSE);

		setNativeLookAndFeel(true);

		Toolkit toolkit = Toolkit.getDefaultToolkit();
		Dimension dim = toolkit.getScreenSize();
		dim.setSize(dim.width * 0.75d, dim.height * 0.75d);

		m_menuBar = new GameMenuBar(this, this);
		setJMenuBar(m_menuBar);

		m_board = new PitchBoardGUI(dim, m_buttonHandler);

		launchGame();

		addWindowListener(new PitchGUIListener());

		// Display the window.
		pack();
		setVisible(showGUI);
	}

	public static PitchGUI createInstance() {
		instance = new PitchGUI("Blood Bowl", false);
		return instance;
	}

	/**
	 * Getter for the "windowOpened" property.
	 * 
	 * @return the windowOpened
	 * 
	 * @author Tomasz Dąbrowski
	 */
	public boolean isWindowOpened() {
		return windowOpened;
	}

	/**
	 * Setter for the "windowOpened" property.
	 * 
	 * @param windowOpened
	 *            the windowOpened to set
	 * 
	 * @author Tomasz Dąbrowski
	 */
	public void setWindowOpened(final boolean windowOpened) {
		this.windowOpened = windowOpened;
	}

	/**
	 * Getter for the "instance" property.
	 * 
	 * @return the instance
	 * 
	 * @author Tomasz Dąbrowski
	 */
	public static PitchGUI getInstance() {
		return instance;
	}

	/**
	 * Initializes the dugout controls as well as the other buttons presented
	 * above the pitch.
	 */
	private void initTopToolbar() {
		// initialize the dugouts
		m_injured1 = new JComboBox();
		m_ko1 = new JComboBox();
		m_reserved1 = new JComboBox();

		m_injured2 = new JComboBox();
		m_ko2 = new JComboBox();
		m_reserved2 = new JComboBox();

		// initialize the top status labels
		m_status = new JLabel();
		m_turn = new JLabel();
		m_score = new JLabel();

		debugStepButton = new JButton();
		debugStepButton.setText("step");
		debugStepButton.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(final ActionEvent arg0) {
				debugStep = true;
			}
		});

		resetButton = new JButton();
		resetButton.setText("reset");
		resetButton.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(final ActionEvent arg0) {
				GameEngine.createInstance().startNewGame(DataLoader.getInstance().getTeamA(), DataLoader.getInstance().getTeamB());
			}
		});

		automaticModeCheckbox = new JCheckBox();
		automaticModeCheckbox.setText("auto");
		automaticModeCheckbox.setSelected(automaticMode);
		automaticModeCheckbox.addChangeListener(new ChangeListener() {

			@Override
			public void stateChanged(final ChangeEvent arg0) {
				setAutomaticMode(automaticModeCheckbox.isSelected());
			}
		});

		intervalButton = new JButton();
		intervalButton.setText("S:" + (INTERVALS.indexOf(getInterval()) + 1));
		intervalButton.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(final ActionEvent e) {
				int idx = INTERVALS.indexOf(getInterval());
				idx = (idx + 1) % INTERVALS.size();
				setInterval(INTERVALS.get(idx));
				intervalButton.setText("S:" + (INTERVALS.indexOf(getInterval()) + 1));
			}
		});

		m_startGame = new JButton();
		m_startGame.addActionListener(m_buttonHandler);
		m_startGame.setText("Start Game");
		m_endTurn = new JButton();
		m_endTurn.addActionListener(m_buttonHandler);
		m_endTurn.setText("End Turn");
	}

	/**
	 * Starts the shutdown thread.
	 * 
	 * 
	 * @author Tomasz Dąbrowski
	 */
	public static void startShutdown() {
		// Thread t = new Thread("PitchGUI - restart thread") {
		// @Override
		// public void run() {
		// synchronized (AIEngine.getThreadsLock()) {
		// // GameEngine.getInstance().onShutdown();
		// // GameEngine.createInstance();
		// GameEngine.getInstance().startNewGame(DataLoader.getInstance().getTeamA(),
		// DataLoader.getInstance().getTeamB());
		// }
		// };
		// };
		// t.setPriority(Thread.MAX_PRIORITY);
		// t.start();
	}

	/**
	 * Getter for the "interval" property.
	 * 
	 * @return the interval
	 * 
	 * @author Tomasz Dąbrowski
	 */
	public static int getInterval() {
		return PitchGUI.interval;
	}

	/**
	 * Setter for the "interval" property.
	 * 
	 * @param interval
	 *            the interval to set
	 * 
	 * @author Tomasz Dąbrowski
	 */
	public static void setInterval(final int interval) {
		PitchGUI.interval = interval;
	}

	/**
	 * Setter for the "automaticMode" property.
	 * 
	 * @param automaticMode
	 *            the automaticMode to set
	 * 
	 * @author Tomasz Dąbrowski
	 */
	private static void setAutomaticMode(final boolean automaticMode) {
		PitchGUI.automaticMode = automaticMode;
		if (!automaticMode) {
			PitchBoardGUI.getInstance().refreshCells();
		}
	}

	/**
	 * Getter for the "automaticMode" property.
	 * 
	 * @return the automaticMode
	 * 
	 * @author Tomasz Dąbrowski
	 */
	public static boolean isAutomaticMode() {
		return PitchGUI.automaticMode;
	}

	public boolean getAndSetDebugStep() {
		while (!debugStep) {
			try {
				Thread.yield();
				Thread.sleep(200);
			} catch (InterruptedException e) {
			}
		}
		try {
			return debugStep;
		} finally {
			debugStep = false;
		}
	}

	/**
	 * Sets the look of the GUI to either native or SWING
	 * 
	 * @param setNative
	 *            - true for native, false for SWING
	 */
	private void setNativeLookAndFeel(final boolean setNative) {
		try {
			if (setNative == true) {
				UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
			}

		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (UnsupportedLookAndFeelException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * Set Dug outs depend on changing their states.
	 * 
	 * @param player
	 *            - the player
	 * @param teamID
	 *            - the player's team ID
	 */
	public void setDougouts(final Player player, final TeamID teamID) {
		if (teamID == TeamID.TEAM_A) {
			if (player.state() == PlayerState.NONE) {
				player.setState(PlayerState.IN_DUGOUT);
				m_reserved1.addItem(player);
			} else if (player.state() == PlayerState.DEAD) {
				m_injured1.addItem(player);
			} else if (player.state() == PlayerState.INJURED) {
				m_ko1.addItem(player);
			} else {
				// Not supposed to happen
				assert (false);
			}
		} else {
			if (player.state() == PlayerState.NONE) {
				player.setState(PlayerState.IN_DUGOUT);
				m_reserved2.addItem(player);
			} else if (player.state() == PlayerState.DEAD) {
				m_injured2.addItem(player);
			} else if (player.state() == PlayerState.INJURED) {
				m_ko2.addItem(player);
			} else {
				// Not supposed to happen
				assert (false);
			}
		}
	}

	/**
	 * Sets the team dugouts.
	 * 
	 * @param teamID
	 *            - the team ID
	 */
	public void setDugouts(final TeamID teamID) {
		switch (teamID) {
		case TEAM_A:
			if (GameEngine.getInstance().teamA() == null) {
				return;
			}

			m_reserved1.removeAllItems();
			m_injured1.removeAllItems();
			m_ko1.removeAllItems();

			for (Player player : GameEngine.getInstance().teamA().players()) {
				if (player.state() == PlayerState.NONE || player.state() == PlayerState.IN_DUGOUT || player.state() == PlayerState.STUNNED) {
					player.setState(PlayerState.IN_DUGOUT);
					m_reserved1.addItem(player);

				} else if (player.state() == PlayerState.DEAD || player.state() == PlayerState.INJURED) {
					m_injured1.addItem(player);
				} else if (player.state() == PlayerState.KO) {
					m_ko1.addItem(player);
				} else {
					// Not supposed to happen
					assert (false);
				}
			}
			break;
		case TEAM_B:
			if (GameEngine.getInstance().teamB() == null) {
				return;
			}

			m_reserved2.removeAllItems();
			m_injured2.removeAllItems();
			m_ko2.removeAllItems();

			for (Player player : GameEngine.getInstance().teamB().players()) {
				if (player.state() == PlayerState.NONE || player.state() == PlayerState.IN_DUGOUT || player.state() == PlayerState.STUNNED) {
					player.setState(PlayerState.IN_DUGOUT);
					m_reserved2.addItem(player);
				} else if (player.state() == PlayerState.DEAD || player.state() == PlayerState.INJURED) {
					m_injured2.addItem(player);
				} else if (player.state() == PlayerState.KO) {
					m_ko2.addItem(player);
				} else {
					// Not supposed to happen
					assert (false);
				}
			}
			break;
		}
	}

	/**
	 * Sets board
	 */
	private void launchGame() {
		m_board.launchGame();
		// Set up the content pane.
		addComponentsToPane(getContentPane());
	}

	/**
	 * Sets the components of the game interface in panels.
	 * 
	 * @param pane
	 *            - the container where all the elements will reside
	 */
	private void addComponentsToPane(final Container pane) {
		initTopToolbar();

		// team menu
		JPanel teamMenu = new JPanel();
		teamMenu.setLayout(new GridLayout(0, 3));

		// team A sub menu
		JPanel teamAMenu = new JPanel();
		teamAMenu.setLayout(new GridLayout(1, 3));

		// Add controls to set up horizontal and vertical gaps
		JPanel container = new JPanel();
		container.setLayout(new GridLayout(2, 1));
		container.add(new JLabel("Dead & Injured"));
		container.add(m_injured1);
		teamAMenu.add(container);

		container = new JPanel();
		container.setLayout(new GridLayout(2, 1));
		container.add(new JLabel("Knocked Out"));
		container.add(m_ko1);
		teamAMenu.add(container);

		container = new JPanel();
		container.setLayout(new GridLayout(2, 1));
		container.add(new JLabel("Reserve"));
		container.add(m_reserved1);
		teamAMenu.add(container);

		// team B sub menu
		JPanel teamBMenu = new JPanel();
		teamBMenu.setLayout(new GridLayout(1, 3));

		// set the TEAM_B dugouts
		container = new JPanel();
		container.setLayout(new GridLayout(2, 1));
		container.add(new JLabel("Dead & Injured"));
		container.add(m_injured2);
		teamBMenu.add(container);

		container = new JPanel();
		container.setLayout(new GridLayout(2, 1));
		container.add(new JLabel("Knocked Out"));
		container.add(m_ko2);
		teamBMenu.add(container);

		container = new JPanel();
		container.setLayout(new GridLayout(2, 1));
		container.add(new JLabel("Reserve"));
		container.add(m_reserved2);
		teamBMenu.add(container);

		// set the status panel
		JPanel statusPanel = new JPanel(new GridLayout(1, 3));
		statusPanel.add(m_status);

		// TODO: replace this with a smart button for start game/end turn
		m_cardsPanel = new JPanel(new CardLayout());
		m_cardsPanel.add(m_startGame, ButtonCardState.START_GAME.toString());
		m_cardsPanel.add(m_endTurn, ButtonCardState.END_TURN.toString());
		statusPanel.add(m_cardsPanel);
		statusPanel.add(resetButton);
		statusPanel.add(debugStepButton);
		statusPanel.add(automaticModeCheckbox);
		statusPanel.add(intervalButton);

		container = new JPanel();
		container.setLayout(new GridLayout(2, 1));
		container.add(m_turn);
		container.add(m_score);
		statusPanel.add(container);

		// Add both team sub menus to the "team menu"
		teamMenu.add(teamAMenu);
		teamMenu.add(statusPanel);
		teamMenu.add(teamBMenu);

		pane.add(teamMenu, BorderLayout.NORTH);
		pane.add(m_board, BorderLayout.CENTER);
		pane.add(LogWindow.instance(), BorderLayout.SOUTH);
	}

	/**
	 * Sets the buttons of the grid with the relevant data.
	 * 
	 * @param x
	 *            ,y - Position of cells on board
	 * @param player
	 *            - a local reference to a player object
	 * @param isBall
	 *            - signals if the location contains only the ball
	 */
	private void setButtonData(final int x, final int y, final Player player, final boolean isBall) {
		m_board.setButtonData(x, y, player, isBall);
	}

	/**
	 * Returns selected player from the reserve
	 * 
	 * @param team
	 *            Team specifying which reserve to select
	 * @return Player selected player - null if none remain
	 */
	public Player getSelectedReserve(final TeamID team) {
		return (Player) (team == TeamID.TEAM_A ? m_reserved1.getSelectedItem() : m_reserved2.getSelectedItem());
	}

	/**
	 * Sets a selected player from the reserve.
	 * 
	 * @param team
	 *            player's team
	 * @param player
	 *            reserve player
	 * 
	 * @author Tomasz Dąbrowski
	 */
	public void setSelectedReserve(final TeamID team, final Player player) {
		if (team == TeamID.TEAM_A) {
			m_reserved1.setSelectedItem(player);
		} else {
			m_reserved2.setSelectedItem(player);
		}
	}

	public void removeSelectedReserve(final TeamID team) {
		JComboBox reserve = (team == TeamID.TEAM_A ? m_reserved1 : m_reserved2);
		reserve.removeItemAt(reserve.getSelectedIndex());

		if (reserve.getItemCount() > 0) {
			reserve.setSelectedIndex(0);
		}
	}

	class ButtonHandler implements ActionListener {

		/**
		 * Action listener for the pitch cells. Handles the placement and
		 * movement of players on the map from the GUI perspective.
		 */
		@Override
		public void actionPerformed(final ActionEvent ae) {
			if (ae.getSource() == m_startGame) {
				GameEngine.createInstance();
				GameEngine.getInstance().startNewGame(DataLoader.getInstance().getTeamA(), DataLoader.getInstance().getTeamB());
			}

			if (ae.getSource() == m_endTurn) {
				GameEngine.getInstance().endTurn();
			}

			if (ae.getSource() instanceof PitchCell) {
				if (GameEngine.getInstance() != null && GameEngine.getInstance().isInitialized()) {
					PitchCell btn = (PitchCell) ae.getSource();
					GameEngine.getInstance().processInput(btn.pos());
				}
			}
		}
	}

	/**
	 * Updates the entire map
	 * 
	 * @param map
	 *            - a reference to the map
	 */
	private void updateGUI(final MapContainer map) {
		for (int i = 0; i < map.getMap().length; ++i) {
			for (int j = 0; j < map.getMap()[i].length; ++j) {
				updateGUI(map, new Position(i, j));
			}
		}
	}

	/**
	 * Updates a single cell of the map.
	 * 
	 * @param map
	 *            - the map container
	 * @param pos
	 *            - the position to update
	 */
	private void updateGUI(final MapContainer map, final Position pos) {
		int code = map.getMap()[pos.x()][pos.y()];
		Player player = map.getPlayer(code);
		boolean isBall = (code == MapContainer.BALL_CODE);

		if (player != null) {
			isBall |= player.hasBall();
		}

		setButtonData(pos.x(), pos.y(), player, isBall);
	}

	/**
	 * Implementation of the IObserver interface. Receives notifications from
	 * the registered subjects.
	 */
	@Override
	public void notifyObserver(final Object sender, final Object msg) {
		if (sender instanceof Pitch) {
			if (msg instanceof PitchUpdateNotification) {
				PitchUpdateNotification notification = (PitchUpdateNotification) msg;
				updateGUI(notification.pitch, notification.pos);
			} else {
				// this is a heavy update, so we reset everything
				reset();

				// update the map
				MapContainer map = (MapContainer) msg;
				updateGUI(map);
			}
		}
	}

	/**
	 * Resets the gui based on all the values of the engine.
	 */
	private void reset() {
		// update the dugouts
		setDugouts(TeamID.TEAM_A);
		setDugouts(TeamID.TEAM_B);

		// TODO: update the menu bar
	}

	/**
	 * Display the status of the game.
	 * 
	 * @param newStatus
	 */
	public void setStatus(final String newStatus) {
		m_status.setText(newStatus);
	}

	public void setTurnCounter(final int turn) {
		m_turn.setText("Turn: " + turn);
	}

	/**
	 * Displays the score
	 * 
	 * @param scoreA
	 *            - team A score
	 * @param scoreB
	 *            - team B score
	 */
	public void setScore(final int scoreA, final int scoreB) {
		m_score.setText("<html>Team A: " + scoreA + "<br>Team B: " + scoreB + "</html>");
	}

	public void updateTopBar(final ButtonCardState state) {
		if (state == ButtonCardState.DISABLED) {
			m_startGame.setEnabled(false);
			m_endTurn.setEnabled(false);
		} else {
			m_startGame.setEnabled(true);
			m_endTurn.setEnabled(true);
			CardLayout cl = (CardLayout) m_cardsPanel.getLayout();
			cl.show(m_cardsPanel, state.toString());
		}

	}

	/**
	 * Adds a selection highlight to the board.
	 * 
	 * @param selObj
	 *            The selection object that specifies which tiles are affected.
	 */
	public void setSelectionHighlight(final SelectionObject selObj) {
		m_board.setSelectionHighlight(selObj);
	}

	/**
	 * Removes a selection highlight to the board
	 * 
	 * @param selObj
	 *            The selection object that needs to be removed.
	 */
	public void removeSelectionHighlight(final SelectionObject selObj) {
		m_board.removeSelectionHighlight(selObj);
	}

	/**
	 * Removes all selection highlights from the board.
	 */
	public void clearSelectionHighlight() {
		m_board.clearSelectionHighlight();
	}

	/**
	 * Sets the current action highlight (manages pre-existence, etc)
	 * 
	 * @param selObj
	 *            The action selection object
	 */
	public void setActionHighlight(final SelectionObject selObj) {
		if (!selObj.equals(m_actionHighlight) && m_actionHighlight != null) {
			removeSelectionHighlight(m_actionHighlight);
		}

		m_actionHighlight = selObj;

		setSelectionHighlight(selObj);
	}

	/**
	 * Removes the action highlight, if any is present.
	 */
	public void clearActionHighlight() {
		if (m_actionHighlight != null) {
			removeSelectionHighlight(m_actionHighlight);
		}

		m_actionHighlight = null;
	}
}

/**
 * PitchGUI windows listener.
 * 
 * @author Tomasz D�browski
 * 
 */
class PitchGUIListener implements WindowListener {

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * java.awt.event.WindowListener#windowActivated(java.awt.event.WindowEvent)
	 */
	@Override
	public void windowActivated(final WindowEvent e) {

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * java.awt.event.WindowListener#windowClosed(java.awt.event.WindowEvent)
	 */
	@Override
	public void windowClosed(final WindowEvent e) {
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * java.awt.event.WindowListener#windowClosing(java.awt.event.WindowEvent)
	 */
	@Override
	public void windowClosing(final WindowEvent e) {
		System.out.println("---");
		PassKnowBase.saveInstances();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * java.awt.event.WindowListener#windowDeactivated(java.awt.event.WindowEvent
	 * )
	 */
	@Override
	public void windowDeactivated(final WindowEvent e) {

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * java.awt.event.WindowListener#windowDeiconified(java.awt.event.WindowEvent
	 * )
	 */
	@Override
	public void windowDeiconified(final WindowEvent e) {

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * java.awt.event.WindowListener#windowIconified(java.awt.event.WindowEvent)
	 */
	@Override
	public void windowIconified(final WindowEvent e) {

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * java.awt.event.WindowListener#windowOpened(java.awt.event.WindowEvent)
	 */
	@Override
	public void windowOpened(final WindowEvent e) {
		PitchGUI.getInstance().setWindowOpened(true);
		// if (PitchGUI.isAutomaticMode()) {
		// GameEngine.getInstance().startNewGame(DataLoader.getInstance().getTeamA(),
		// DataLoader.getInstance().getTeamB());
		// }
	}

}